package
{
	import com.adobe.utils.AGALMiniAssembler;
	import com.bayvakoof.dewall.DelaunayTriangulator;
	import com.bayvakoof.dewall.geom.BezierCurve;
	import com.bayvakoof.dewall.geom.CompositeCurve;
	import com.bayvakoof.dewall.geom.GeometricUtil;
	import com.bayvakoof.dewall.geom.Triangle;
	
	import flash.display.GraphicsPath;
	import flash.display.GraphicsPathCommand;
	import flash.display.Sprite;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.VertexBuffer3D;
	import flash.events.Event;
	import flash.geom.Matrix3D;
	import flash.geom.Point;
	import flash.geom.Vector3D;
	import flash.text.Font;
	import flash.utils.getTimer;
	
	import wumedia.vector.VectorText;
	
	public class FlexVectorArt extends Sprite
	{
		protected var context3D:Context3D;
		protected var program:Program3D;
		protected var vertexbuffer:VertexBuffer3D;
		protected var indexbuffer:IndexBuffer3D;
		
		public function FlexVectorArt()
		{
			stage.stage3Ds[0].addEventListener( Event.CONTEXT3D_CREATE, initMolehill );
			stage.stage3Ds[0].requestContext3D();
			
			addEventListener(Event.ENTER_FRAME, onRender);
		}
		
		protected function initMolehill(e:Event):void
		{
			//init font
			var paths:Vector.<GraphicsPath> = new Vector.<GraphicsPath>();
			Font.registerFont(Font0);
			//Font.registerFont(Font1);
			VectorText.extractFont(root.loaderInfo.bytes, ["_Arial"], true);
			
			var currentPath:GraphicsPath;
			var myGraphics:Object = new Object();
			myGraphics.moveTo = function(x:Number, y:Number):void
			{
				currentPath = null;
				for each(var graphicsPath:GraphicsPath in paths)
				{
					if(graphicsPath.data[graphicsPath.data.length - 2] == x && graphicsPath.data[graphicsPath.data.length - 1] == y)
					{
						currentPath = graphicsPath;
						break;
					}
				}
				
				if(currentPath == null)
				{
					currentPath = new GraphicsPath();
					currentPath.moveTo(x, y);
					paths.push(currentPath);
				}
			};
			
			myGraphics.lineTo = function(x:Number, y:Number):void
			{
				currentPath.lineTo(x, y);
				if(x == currentPath.data[0] && y == currentPath.data[1])
				{
					currentPath = null;
				}
			};
			
			myGraphics.curveTo = function(cx:Number, cy:Number, x:Number, y:Number):void
			{
				currentPath.curveTo(cx, cy, x, y);
				if(x == currentPath.data[0] && y == currentPath.data[1])
				{
					currentPath = null;
				}
			};
			//load text
			VectorText.write(myGraphics, "_Arial", 1024, 0, 0, "abc", 0, 0, 3000, VectorText.TOP_LEFT, false);
			
			var points:Vector.<Point> = new Vector.<Point>();//use for constrained delaunay triangulation
			var constrainedEdges:Vector.<Point> = new Vector.<Point>();
			var curves:Vector.<CompositeCurve> = new Vector.<CompositeCurve>();
			
			for each(var graphicsPath:GraphicsPath in paths)
			{
				var idx: int = 0;
				var compositeCurve:CompositeCurve = new CompositeCurve();
				var p2:Point;
				var p1: Point;
				var p0:Point;
				var sp:Point;
				for(var i: int = 0; i < graphicsPath.commands.length; i++)
				{
					if(graphicsPath.commands[i] == GraphicsPathCommand.MOVE_TO)
					{
						idx+=2;
						sp = p0 = p1 = new Point(graphicsPath.data[idx-2], graphicsPath.data[idx-1]);
						points.push(p1);
					}
					if(graphicsPath.commands[i] == GraphicsPathCommand.LINE_TO)
					{
						var bc:BezierCurve = new BezierCurve();
						bc.type = BezierCurve.LINEAR;
						bc.sx = graphicsPath.data[idx-2];
						bc.sy = graphicsPath.data[idx-1];
						bc.x = graphicsPath.data[idx];
						bc.y = graphicsPath.data[idx+1];
						compositeCurve.beziers.push(bc);
						
						if(i == graphicsPath.commands.length - 1)
						{
							p1 = sp;
						}
						else
						{
							p1 = new Point(bc.x, bc.y);
							points.push(p1);
						}
						
						constrainedEdges.push(p0, p1);
						p0=p1;
						idx += 2;
					}
					else if(graphicsPath.commands[i] == GraphicsPathCommand.CURVE_TO)
					{
						var bc:BezierCurve = new BezierCurve();
						bc.type = BezierCurve.QUADRATIC;
						bc.sx = graphicsPath.data[idx-2];
						bc.sy = graphicsPath.data[idx-1];
						bc.cx = graphicsPath.data[idx];
						bc.cy = graphicsPath.data[idx+1];
						bc.x = graphicsPath.data[idx+2];
						bc.y = graphicsPath.data[idx+3];
						compositeCurve.beziers.push(bc);
						
						p1 = new Point(bc.cx, bc.cy);
						if(i == graphicsPath.commands.length - 1)
						{
							p2 = sp;
							points.push(p1);
						}
						else
						{
							p2 = new Point(bc.x, bc.y);
							points.push(p1, p2);
						}
						
						constrainedEdges.push(p0, p1, p1, p2, p2, p0);
						p0 = p2;
						idx += 4;
					}
				}
				
				curves.push(compositeCurve);
			}
			
			var triangulator: DelaunayTriangulator = new DelaunayTriangulator();
			var triangles:Vector.<Triangle> = triangulator.generate(points, constrainedEdges);
			var quadraticBeziers:Vector.<BezierCurve> = new Vector.<BezierCurve>();
			var convexBeziers:Vector.<BezierCurve> = new Vector.<BezierCurve>();
			var concaveBeziers:Vector.<BezierCurve> = new Vector.<BezierCurve>();
			var polygons:Vector.<Vector.<Point>> = new Vector.<Vector.<Point>>();
			
			for(var i: int = triangles.length - 1; i >= 0; i--)
			{
				var tri:Triangle = triangles[i];
				for each(var curve:CompositeCurve in curves)
				{
					for each(var bc:BezierCurve in curve.beziers)
					{
						if(bc.type == BezierCurve.QUADRATIC)
						{
							if(((tri.v1.x == bc.sx && tri.v1.y == bc.sy) || (tri.v1.x == bc.cx && tri.v1.y == bc.cy) || (tri.v1.x == bc.x && tri.v1.y == bc.y))
								&& ((tri.v2.x == bc.sx && tri.v2.y == bc.sy) || (tri.v2.x == bc.cx && tri.v2.y == bc.cy) || (tri.v2.x == bc.x && tri.v2.y == bc.y))
								&& ((tri.v3.x == bc.sx && tri.v3.y == bc.sy) || (tri.v3.x == bc.cx && tri.v3.y == bc.cy) || (tri.v3.x == bc.x && tri.v3.y == bc.y)))
							{
								quadraticBeziers.push(bc);
								triangles.splice(i, 1);
							}
						}
					}
				}
			}
			
			for each(var curve:CompositeCurve in curves)
			{
				polygons.push(curve.getPolygon());
			}
			
			for each(var curve:CompositeCurve in curves)
			{
				for(var i:int = triangles.length - 1; i >= 0; i--)
				{
					var tri:Triangle = triangles[i];
					var center:Point = new Point();
					center.x = (tri.v1.x + tri.v2.x + tri.v3.x)/3;
					center.y = (tri.v1.y + tri.v2.y + tri.v3.y)/3;
					if(GeometricUtil.pnpolys(polygons, center) == false)
					{
						triangles.splice(i, 1);
					}
				}
			}
			
			for each(var bc:BezierCurve in quadraticBeziers)
			{
				if(GeometricUtil.pnpolys(polygons, new Point(bc.cx, bc.cy)) == true)
				{
					concaveBeziers.push(bc);
				}
				else
				{
					convexBeziers.push(bc);
				}
			}
			
			//init 3d
			context3D = stage.stage3Ds[0].context3D;			
			context3D.configureBackBuffer(800, 600, 8, true);
			
			var scale:Number = 1/1024;
			var offset: Number = -0.5;
			var vertices:Vector.<Number> = new Vector.<Number>();
			var indices:Vector.<uint> = new Vector.<uint>();
			var numVertices:uint = 0;
			var r:Number = 1;
			var g:Number = 0;
			var b:Number = 0;
			for each(var tri:Triangle in triangles)
			{
				vertices.push(tri.v1.x*scale + offset, tri.v1.y*scale + offset, 0, r, g, b, 1, 1, 1,//x, y, z, r, g, b, u, v, c
				tri.v2.x*scale + offset, tri.v2.y*scale + offset, 0, r, g, b, 1, 1, 1,
				tri.v3.x*scale + offset, tri.v3.y*scale + offset, 0, r, g, b, 1, 1, 1);
				indices.push(numVertices, numVertices+1, numVertices+2);
				numVertices += 3;
			}
			
			for each(var bc:BezierCurve in convexBeziers)
			{
				vertices.push(bc.sx*scale + offset, bc.sy*scale + offset, 0, r, g, b, 0, 0, -1,
							  bc.cx*scale + offset, bc.cy*scale + offset, 0, r, g, b, 0.5, 0, -1,
							  bc.x*scale + offset, bc.y*scale + offset, 0, r, g, b, 1, 1, -1);
				indices.push(numVertices, numVertices+1, numVertices+2);
				numVertices += 3;
			}
			
			for each(var bc:BezierCurve in concaveBeziers)
			{
				vertices.push(bc.sx*scale + offset, bc.sy*scale + offset, 0, r, g, b, 0, 0, 1,
					bc.cx*scale + offset, bc.cy*scale + offset, 0, r, g, b, 0.5, 0, 1,
					bc.x*scale + offset, bc.y*scale + offset, 0, r, g, b, 1, 1, 1);
				indices.push(numVertices, numVertices+1, numVertices+2);
				numVertices += 3;
			}
			
			// Create VertexBuffer3D. 3 vertices, of 5 Numbers each
			vertexbuffer = context3D.createVertexBuffer(numVertices, 9);
			// Upload VertexBuffer3D to GPU. Offset 0, 3 vertices
			vertexbuffer.uploadFromVector(vertices, 0, numVertices);				
			
			//var indices:Vector.<uint> = Vector.<uint>([0, 1, 2]);
			
			// Create IndexBuffer3D. Total of 3 indices. 1 triangle of 3 vertices
			indexbuffer = context3D.createIndexBuffer(numVertices);			
			// Upload IndexBuffer3D to GPU. Offset 0, count 3
			indexbuffer.uploadFromVector (indices, 0, numVertices);			
			
			var vertexShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler();
			vertexShaderAssembler.assemble( Context3DProgramType.VERTEX,
				"m44 op, va0, vc0\n" + // pos to clipspace
				"mov v0, va1\n" + // copy color
				"mov v1, va2\n" // copy uv
			);			
			
			var fragmentShaderAssembler : AGALMiniAssembler= new AGALMiniAssembler();
			fragmentShaderAssembler.assemble( Context3DProgramType.FRAGMENT,
				"mul ft1.w, v1.x, v1.x\n" +
				"sub ft1.w, ft1.w, v1.y\n" +
				"mul ft1.w, ft1.w, v1.z\n" +
				"kil ft1.w\n" +
				"mov oc, v0"
			);
			
			program = context3D.createProgram();
			program.upload( vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);
		}	
		
		protected function onRender(e:Event):void
		{
			if ( !context3D ) 
				return;
			
			context3D.clear ( 1, 1, 1, 1 );
			
			// vertex position to attribute register 0
			context3D.setVertexBufferAt (0, vertexbuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
			// color to attribute register 1
			context3D.setVertexBufferAt(1, vertexbuffer, 3, Context3DVertexBufferFormat.FLOAT_3);
			// uv to attribute register 1
			context3D.setVertexBufferAt(2, vertexbuffer, 6, Context3DVertexBufferFormat.FLOAT_3);
			// assign shader program
			context3D.setProgram(program);
			
			var m:Matrix3D = new Matrix3D();
			m.appendRotation(getTimer()/40, Vector3D.Z_AXIS);
			context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m, true);
			//context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.<Number>([c5, c4, c3, 1]));
			//context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([c2, c1, c0, 1]));
			
			context3D.drawTriangles(indexbuffer);
			
			context3D.present();			
		}
	}
}